﻿using System;

namespace Ewk.Math.Numerics
{
    /// <summary>
    /// Represents a <c>Complex</c> number.
    /// </summary>
    public struct Complex : IComparable, IFormattable, IConvertible, IComparable<Complex>, IEquatable<Complex>
    {
        private readonly double _real;
        private readonly double _imaginary;

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="real">The real part of the <c>Complex</c> number.</param>
        /// <param name="imaginary">The imaginary part of the <c>Complex</c> number.</param>
        public Complex(double real, double imaginary)
        {
            _real = real;
            _imaginary = imaginary;
        }

        /// <summary>
        /// Gets the real part of the <c>Complex</c> number.
        /// </summary>
        public double Real
        {
            get { return _real; }
        }

        /// <summary>
        /// Gets the imaginary part of the <c>Complex</c> number.
        /// </summary>
        public double Imaginary
        {
            get { return _imaginary; }
        }

        /// <summary>
        /// Gets the modulus of this <c>Complex</c> number.
        /// </summary>
        /// <seealso cref="Argument"/>
        public double Modulus
        {
            get { return System.Math.Sqrt(ModulusSquared); }
        }

        /// <summary>
        /// Gets the squared value of the modulus of the <c>Complex</c> number.
        /// </summary>
        public double ModulusSquared
        {
            get
            {
                return Real*Real +
                       Imaginary*Imaginary;
            }
        }

        /// <summary>
        /// Gets the argument of this <c>Complex</c> number.
        /// </summary>
        public double Argument
        {
            get
            {
                if (IsReal && Real < 0)
                {
                    return System.Math.PI;
                }

                return IsRealNonnegative
                           ? 0
                           : System.Math.Atan2(Imaginary, Real);
            }
        }

        /// <summary>
        /// Indicates whether the <c>Complex</c> is zero.
        /// </summary>
        public bool IsZero
        {
            get
            {
                return System.Math.Abs(Real - 0.0) < double.Epsilon &&
                       System.Math.Abs(Imaginary - 0.0) < double.Epsilon;
            }
        }

        /// <summary>
        /// Indicates whether the <c>Complex</c> is one.
        /// </summary>
        public bool IsOne
        {
            get
            {
                return System.Math.Abs(Real - 1.0) < double.Epsilon &&
                       System.Math.Abs(Imaginary - 0.0) < double.Epsilon;
            }
        }

        /// <summary>
        /// Indicates whether the <c>Complex</c> is the imaginary unit.
        /// </summary>
        public bool IsI
        {
            get
            {
                return System.Math.Abs(Real - 0.0) < double.Epsilon &&
                       System.Math.Abs(Imaginary - 1.0) < double.Epsilon;
            }
        }

        /// <summary>
        /// Indicates whether the provided <c>Complex</c> evaluates to a
        /// value that is not a number.
        /// </summary>
        public bool IsNaN
        {
            get
            {
                return double.IsNaN(Real) ||
                       double.IsNaN(Imaginary);
            }
        }

        /// <summary>
        /// Indicates the provided <c>Complex</c> evaluates to an infinite value.
        /// </summary>
        /// <remarks>
        /// True if it either evaluates to a complex infinity or to a directed infinity.
        /// </remarks>
        public bool IsInfinity
        {
            get
            {
                return double.IsInfinity(Real) ||
                       double.IsInfinity(Imaginary);
            }
        }

        /// <summary>
        /// Indicates the provided <c>Complex</c> is real.
        /// </summary>
        public bool IsReal
        {
            get { return System.Math.Abs(Imaginary - 0.0) < double.Epsilon; }
        }

        /// <summary>
        /// Indicates the provided <c>Complex</c> is imaginary.
        /// </summary>
        public bool IsImaginary
        {
            get { return System.Math.Abs(Real - 0.0) < double.Epsilon; }
        }

        /// <summary>
        /// Indicates the provided <c>Complex</c> is real and not negative, that is >= 0.
        /// </summary>
        public bool IsRealNonnegative
        {
            get
            {
                return IsReal &&
                       Real >= 0.0;
            }
        }

        /// <summary>
        /// Creates a <see cref="string"/> representation  of the <see cref="Complex"/> instance.
        /// </summary>
        /// <returns>A <see cref="string"/> representation  of the <see cref="Complex"/> instance.</returns>
        public override string ToString()
        {
            return Real +  " + " + Imaginary + "i";
        }

        /// <summary>
        /// Creates a <see cref="string"/> representation of the <c>Complex</c> number.
        /// </summary>
        /// <param name="format">The format to use for the string conversion.</param>
        /// <param name="formatProvider">The formatprovider to use for the string conversion.</param>
        /// <returns>A <see cref="string"/> representation of the <c>Complex</c> number.</returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            return Real.ToString(format, formatProvider) + " + " + Imaginary.ToString(format, formatProvider) + "i";
        }

        #region Implementation of IConvertible
        TypeCode IConvertible.GetTypeCode()
        {
            return ((IConvertible)Real).GetTypeCode();
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToBoolean(provider);
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToChar(provider);
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToSByte(provider);
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToByte(provider);
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToInt16(provider);
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToUInt16(provider);
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToInt32(provider);
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToUInt32(provider);
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToInt64(provider);
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToUInt64(provider);
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToSingle(provider);
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToDouble(provider);
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToDecimal(provider);
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            return ((IConvertible)Real).ToDateTime(provider);
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            return ToString("", provider);
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)Real).GetTypeCode();
        }
        #endregion

        #region Equality
        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="Complex"/> represent the same value.
        /// </summary>
        /// <param name="other">The <see cref="Complex"/> number to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="Complex"/> represent the same value.</returns>
        public bool Equals(Complex other)
        {
            return other.Real.Equals(Real) &&
                   other.Imaginary.Equals(Imaginary);
        }

        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="object"/> represent the same value.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="object"/> represent the same value.</returns>
        public override bool Equals(object obj)
        {
            return !ReferenceEquals(null, obj) &&
                   obj is Complex &&
                   Equals((Complex) obj);
        }

        /// <summary>
        /// Serves as a hash function.
        /// </summary>
        /// <returns>An <see cref="int"/> that represents the hash of this instance.</returns>
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        /// <summary>
        /// Compares this instance to the specified <see cref="Complex"/> and
        /// returns an <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="Complex"/>.
        /// </summary>
        /// <param name="other">The <see cref="Complex"/> to compare with.</param>
        /// <returns>
        /// An <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="Complex"/>.
        /// </returns>
        public int CompareTo(Complex other)
        {
            var res = Modulus.CompareTo(other.Modulus);

            return res == 0
                       ? Argument.CompareTo(other.Argument)
                       : res;
        }

        /// <summary>
        /// Compares this instance to the specified <see cref="object"/> and
        /// returns an <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>
        /// An <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="object"/>.
        /// </returns>
        public int CompareTo(object obj)
        {
            return obj is Complex
                       ? CompareTo((Complex) obj)
                       : 1;
        }

        #endregion

        #region Static members
        /// <summary>
        /// Represents the zero value. This field is constant.
        /// </summary>
        public static Complex Zero
        {
            get { return new Complex(0, 0); }
        }

        /// <summary>
        /// Represents the <c>1</c> value. This field is constant.
        /// </summary>
        public static Complex One
        {
            get { return new Complex(1, 0); }
        }

        /// <summary>
        /// Represents the imaginary unit number. This field is constant.
        /// </summary>
        public static Complex I
        {
            get { return new Complex(0, 1); }
        }

        /// <summary>
        /// Represents a value that is not a number. This field is constant.
        /// </summary>
        public static Complex NaN
        {
            get { return new Complex(double.NaN, double.NaN); }
        }

        /// <summary>
        /// Represents the infinity value
        /// </summary>
        public static Complex Infinity
        {
            get { return new Complex(double.PositiveInfinity, double.PositiveInfinity); }
        }

        /// <summary>
        /// Calculates the conjugate of the specified <see cref="Complex"/>.
        /// </summary>
        /// <param name="complex">The <see cref="Complex"/> to calculate the conjugate for.</param>
        /// <returns>A <see cref="Complex"/> that represents the conjugate of the specified <see cref="Complex"/>.</returns>
        public static Complex Conjugate(Complex complex)
        {
            return new Complex(
                complex.Real,
                -complex.Imaginary);
        }

        #region Computing

        /// <summary>
        /// Adds two <see cref="Complex"/> instances.
        /// </summary>
        public static Complex Add(Complex a, Complex b)
        {
            return new Complex(
                a.Real + b.Real,
                a.Imaginary + b.Imaginary);
        }

        /// <summary>
        /// Subtracts <see cref="Complex">b</see> from <see cref="Complex">a</see>.
        /// </summary>
        public static Complex Subtract(Complex a, Complex b)
        {
            return new Complex(
                a.Real - b.Real,
                a.Imaginary - b.Imaginary);
        }

        /// <summary>
        /// Multiplies two <see cref="Complex"/> instances.
        /// </summary>
        public static Complex Multiply(Complex a, Complex b)
        {
            return new Complex(
                a.Real * b.Real - a.Imaginary * b.Imaginary,
                a.Real * b.Imaginary + a.Imaginary * b.Real);
        }

        /// <summary>
        /// Divides <see cref="Complex">a</see> by <see cref="Complex">b</see>.
        /// </summary>
        public static Complex Divide(Complex a, Complex b)
        {
            var numerator = a*Conjugate(b);
            var denominator = b.Real*b.Real + b.Imaginary*b.Imaginary;

            return new Complex(numerator.Real / denominator, numerator.Imaginary / denominator);
        }

        private static readonly double _1OverSqrt2 = System.Math.Sqrt(2.0) / 2.0;
        /// <summary>
        /// The Square Root (power 1/2) of this <c>Complex</c>
        /// </summary>
        public static Complex Sqrt(Complex value)
        {
            if (value.IsRealNonnegative)
            {
                return new Complex(
                    System.Math.Sqrt(value.Real),
                    0);
            }

            var mod = value.Modulus;

            var real = _1OverSqrt2 * System.Math.Sqrt(mod + value.Real);
            var imaginary = _1OverSqrt2 * System.Math.Sqrt(mod - value.Real);

            return (value.Imaginary > 0 || value.IsReal && value.Real < 0)
                       ? new Complex(real, imaginary)
                       : new Complex(real, -1*imaginary);
        }

        /// <summary>
        /// Returns a specified number raised to the specified power.
        /// </summary>
        /// <param name="x">A <see cref="Complex"/> number to be raised to a power. </param>
        /// <param name="y">A <see cref="Complex"/> number that specifies a power.</param>
        /// <returns>The number x raised to the power y.</returns>
        public static Complex Pow(Complex x, Complex y)
        {
            if (x.IsZero)
            {
                if (y.IsZero)
                {
                    return One;
                }

                if (y.Real > 0)
                {
                    return Zero;
                }

                if (y.Real < 0)
                {
                    return y.IsReal 
                        ? new Complex(double.PositiveInfinity, 0) 
                        : new Complex(double.PositiveInfinity, double.PositiveInfinity);
                }

                return NaN;
            }

            return Exp(y * Log(x));
        }

        /// <summary>
        /// Returns e raised to the specified power.
        /// </summary>
        /// <param name="x">A <see cref="Complex"/> number that specifies a power.</param>
        /// <returns>The number e raised to the power x.</returns>
        public static Complex Exp(Complex x)
        {
            var exp = System.Math.Exp(x.Real);

            return x.IsReal
                       ? new Complex(exp, 0)
                       : new Complex(
                             exp*System.Math.Cos(x.Imaginary),
                             exp*System.Math.Sin(x.Imaginary)
                             );
        }

        /// <summary>
        /// Calculates the (natural base) logarithm of the specified <see cref="Complex"/>.
        /// </summary>
        /// <param name="value">The <see cref="Complex"/> to calculate the logarithm for.</param>
        /// <returns>The (natural base) logarithm of the specified <see cref="Complex"/>.</returns>
        public static Complex Log(Complex value)
        {
            if (value.IsRealNonnegative)
            {
                return new Complex(System.Math.Log(value.Real), 0);
            }

            return new Complex(
                0.5 * System.Math.Log(value.ModulusSquared),
                value.Argument
                );
        }

        #endregion

        /// <summary>
        /// Creates a <see cref="Complex"/> from a modulus and an argument.
        /// </summary>
        /// <param name="modulus">The modulus to use.</param>
        /// <param name="argument">The argument to use.</param>
        /// <returns>A <see cref="Complex"/> that represesnts the specified modulus and argument.</returns>
        public static Complex CreateFromModulusArgument(double modulus, double argument)
        {
            if (modulus < 0.0)
            {
                throw new ArgumentOutOfRangeException("modulus", modulus, "The modulus cannot be negative.");
            }

            return new Complex(
                modulus * System.Math.Cos(argument),
                modulus * System.Math.Sin(argument)
                );
        }

        #region Overloaded operators
        /// <summary>
        /// Adds two <see cref="Complex"/> instances.
        /// </summary>
        public static Complex operator +(Complex a, Complex b)
        {
            return Add(a, b);
        }

        /// <summary>
        /// Subtracts <see cref="Complex">b</see> from <see cref="Complex">a</see>.
        /// </summary>
        public static Complex operator -(Complex a, Complex b)
        {
            return Subtract(a, b);
        }

        /// <summary>
        /// Multiplies two <see cref="Complex"/> instances.
        /// </summary>
        public static Complex operator *(Complex a, Complex b)
        {
            return Multiply(a, b);
        }

        /// <summary>
        /// Divides <see cref="Complex">a</see> by <see cref="Complex">b</see>.
        /// </summary>
        public static Complex operator /(Complex a, Complex b)
        {
            return Divide(a, b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Complex">a</see> is equal to <see cref="Complex">b</see>.
        /// </summary>
        public static bool operator ==(Complex a, Complex b)
        {
            return a.Equals(b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Complex">a</see> is not equal to <see cref="Complex">b</see>.
        /// </summary>
        public static bool operator !=(Complex a, Complex b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Complex">a</see> is smaller than <see cref="Complex">b</see>.
        /// </summary>
        public static bool operator <(Complex a, Complex b)
        {
            return a.CompareTo(b) < 0;
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Complex">a</see> is greater than <see cref="Complex">b</see>.
        /// </summary>
        public static bool operator >(Complex a, Complex b)
        {
            return a.CompareTo(b) > 0;
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Complex">a</see> is equal to or smaller than <see cref="Complex">b</see>.
        /// </summary>
        public static bool operator <=(Complex a, Complex b)
        {
            return !(a > b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Complex">a</see> is equal to or greater than <see cref="Complex">b</see>.
        /// </summary>
        public static bool operator >=(Complex a, Complex b)
        {
            return !(a < b);
        }

        /// <summary>
        /// Implicitly creates a <see cref="Complex"/> from a <see cref="double"/>.
        /// </summary>
        public static implicit operator Complex(double value)
        {
            return new Complex(value, 0.0);
        }

        /// <summary>
        /// Implicitly creates a <see cref="Complex"/> from a <see cref="int"/>.
        /// </summary>
        public static implicit operator Complex(int value)
        {
            return new Complex(value, 0.0);
        }
        #endregion

        #endregion
    }
}